using System;
using System.Diagnostics;
using System.Reflection;
using System.IO;
using System.Globalization;
using System.Xml;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using System.Text.RegularExpressions;
using AUSRIS.Reports.Utilities.Deployment.Entities;

namespace AUSRIS.Reports.Utilities.Deployment.Configuration.Xml
{
	public class XmlConfigurationDatabase : IConfigurationDatabase
	{
		#region Private Fields

		private StringDictionary globalVariables;
		private static readonly Regex GlobalsRegex = new Regex(@"(?<g>\${(?<k>[^}]+)})", RegexOptions.Compiled | RegexOptions.IgnoreCase);
		private string configFileDirectory;
		private Dictionary<string, DataSource> datasources;

		#endregion

		#region Public Properties

		public string ConfigFileDirectory
		{
			get
			{
				return configFileDirectory;
			}
			set
			{
				configFileDirectory = value;
			}
		}

		#endregion

		#region IConfigurationDatabase Members

		public DeploymentSet GetDeploymentSet(string name)
		{
			this.datasources = new Dictionary<string, DataSource>();
			string filePath = Path.Combine(this.configFileDirectory, name + ".xml");
			XmlDocument document = null;
			FileInfo f = new FileInfo(filePath);
			using ( FileStream reader = f.OpenRead() )
			{
				document = new XmlDocument();
				document.Load(reader);
			}
			this.ReadGlobals(document);
			DeploymentSet deploymentSet = this.LoadDeploymentSet(document);
			return deploymentSet;
		}

		#endregion

		#region Private Methods

		private DeploymentSet LoadDeploymentSet(XmlDocument document)
		{
			XmlNodeList deploymentSetNodes = document.SelectNodes("//deployment");
			if ( deploymentSetNodes == null || deploymentSetNodes.Count == 0 )
			{
				throw new Exception("Deployment Not Found");
			}
			XmlNode deploymentNode = deploymentSetNodes[0];
			DeploymentSet deploymentSet = new DeploymentSet();
			deploymentSet.Name = this.NodeAttribute(deploymentNode, "name");
			deploymentSet.VersionTag = this.NodeAttribute(deploymentNode, "version-tag");
			deploymentSet.ModuleName = this.NodeAttribute(deploymentNode, "module-name");
			deploymentSet.TargetServiceUrl = this.NodeAttribute(deploymentNode, "target-service-url");
			XmlNodeList folderNodes = deploymentNode.SelectNodes("folders/folder");
			if ( folderNodes == null )
			{
				throw new Exception("Folders Node Not Found");
			}

			foreach ( XmlNode folderNode in folderNodes )
			{
				this.CreateFolder(folderNode, deploymentSet);
			}

			foreach ( XmlNode folderNode in folderNodes )
			{
				this.AddDataSources(folderNode, deploymentSet);
			}

			foreach ( XmlNode folderNode in folderNodes )
			{
				this.AddFolderContents(folderNode, deploymentSet);
			}

			foreach ( XmlNode folderNode in folderNodes )
			{
				this.AddFolderProperties(folderNode, deploymentSet);
			}

			return deploymentSet;
		}

		private void CreateFolder(XmlNode folderNode, DeploymentSet deploymentSet)
		{
			string path = this.NodeAttribute(folderNode, "path");
			deploymentSet.Folders.AddFoldersInPath(path);
		}

		private void AddDataSources(XmlNode folderNode, DeploymentSet deploymentSet)
		{
			string path = this.NodeAttribute(folderNode, "path");
			Folder folder = deploymentSet.Folders[path];
			XmlNodeList dataSourceNodes = folderNode.SelectNodes("data-sources/data-source");
			if ( dataSourceNodes != null )
			{
				foreach ( XmlNode dataSourceNode in dataSourceNodes )
				{
					this.AddDataSource(dataSourceNode, folder);
				}
			}
		}

		private void AddFolderProperties(XmlNode folderNode, DeploymentSet deploymentSet)
		{
			string path = this.NodeAttribute(folderNode, "path");
			Folder folder = deploymentSet.Folders[path];
			this.SetOptionalItemProperties(folder, folderNode);
		}

		private void AddFolderContents(XmlNode folderNode, DeploymentSet deploymentSet)
		{
			string path = this.NodeAttribute(folderNode, "path");
			Folder folder = deploymentSet.Folders[path];
			XmlNodeList reportNodes = folderNode.SelectNodes("reports/report");
			if ( reportNodes != null )
			{
				foreach ( XmlNode reportNode in reportNodes )
				{
					this.AddReport(reportNode, folder);
				}
			}
			XmlNodeList resourceNodes = folderNode.SelectNodes("resources/resource");
			if ( resourceNodes != null )
			{
				foreach ( XmlNode resourceNode in resourceNodes )
				{
					this.AddResource(resourceNode, folder);
				}
			}
			XmlNodeList modelNodes = folderNode.SelectNodes("models/model");
			if ( modelNodes != null )
			{
				foreach ( XmlNode modelNode in modelNodes )
				{
					this.AddModel(modelNode, folder);
				}
			}
		}

		private void AddResource(XmlNode resourceNode, Folder folder)
		{
			string name = this.NodeAttribute(resourceNode, "name");
			string mimeType = this.NodeAttribute(resourceNode, "mime-type");
			XmlNode sourceNode = resourceNode.SelectSingleNode("source-path");
			string sourcePath = this.NodeValue(sourceNode);
			Resource resource = new Resource(name, folder, sourcePath, mimeType);
			this.SetOptionalItemProperties(resource, resourceNode);
			folder.Files.Add(resource);
		}

		private void AddModel(XmlNode modelNode, Folder folder)
		{
			string name = this.NodeAttribute(modelNode, "name");
			XmlNode sourceNode = modelNode.SelectSingleNode("source-path");
			string sourcePath = this.NodeValue(sourceNode);
			Model model = new Model(name, folder, sourcePath);
			
			string dataSourceName = this.NodeAttribute(modelNode, "data-source");
			if ( !this.datasources.ContainsKey(dataSourceName) )
			{
				throw new Exception(string.Format("unknown data-source '{0}' specified in {1}", dataSourceName, model.Name));
			}
			model.DataSource = this.datasources[dataSourceName];

			XmlNode dsvNode = modelNode.SelectSingleNode("datasourceview-path");
			model.DataSourceViewPath = this.NodeValue(dsvNode);

			this.SetOptionalItemProperties(model, modelNode);

			folder.Files.Add(model);
		}

		private void AddReport(XmlNode reportNode, Folder folder)
		{
			if ( reportNode.Attributes["name"] == null )
			{
				throw new Exception(string.Format("Report with no name specified : {0}", reportNode.InnerText));
			}
			string name = this.NodeAttribute(reportNode, "name");
			
			string collapsedHeight = "";
			if ( reportNode["collapsed-height"] != null )
			{
				collapsedHeight = this.NodeAttribute(reportNode, "collapsed-height");
			}

			int cacheTime = 0;
			if ( reportNode["cache-time"] != null )
			{
				cacheTime = int.Parse(this.NodeAttribute(reportNode, "cache-time"));
			}
			
			XmlNode sourceNode = reportNode.SelectSingleNode("source-path");
			string sourcePath = this.NodeValue(sourceNode);

			Report report = new Report(name, folder, sourcePath, collapsedHeight, cacheTime);
			this.SetOptionalItemProperties(report, reportNode);

			if ( reportNode.Attributes["data-source"] == null )
			{
				throw new Exception(string.Format("data-source not specified for report {0}", name));
			}
			string dataSourceName = this.NodeAttribute(reportNode, "data-source");
			if ( !this.datasources.ContainsKey(dataSourceName) )
			{
				throw new Exception(string.Format("unknown data-source '{0}' specified in {1}", dataSourceName, report.Name));
			}
			report.DataSource = this.datasources[dataSourceName];

			XmlNodeList parameterNodes = reportNode.SelectNodes("parameter");
			if ( parameterNodes != null )
			{
				foreach ( XmlNode node in parameterNodes )
				{
					this.AddParameter(node, report);
				}
			}
			
			folder.Files.Add(report);
		}

		private void AddPolicy(XmlNode fromNode, DeploymentItem item)
		{
			ItemPolicy policy = new ItemPolicy(this.NodeAttribute(fromNode, "group-user-name"));
			string[] roleNames = fromNode.Attributes["roles"].Value.Split(',');
			foreach ( string roleName in roleNames )
			{
				policy.Roles.Add(new ItemPolicyRole(roleName.Trim()));
			}
			item.Policies.Add(policy);
		}

		private void AddParameter(XmlNode node, Report report)
		{
			string name = this.NodeAttribute(node, "name");
			XmlNode promptNode = node.Attributes["prompt-user"];
			bool promptUser = false;
			if (promptNode != null && promptNode.Value.Length > 0)
			{
				promptUser = bool.Parse(this.ProcessGlobals(promptNode.Value));
			}
			XmlNode defaultNode = node.Attributes["default-values"];
			string[] defaults = null;
			if (defaultNode != null)
			{
				defaults = node.Attributes["default-values"].Value.Split(',');
			}
			Parameter parameter = new Parameter(name, promptUser, defaults);
			report.Parameters.Add(parameter);
		}

		private void AddDataSource(XmlNode node, Folder folder)
		{
			string name = null;
			string userName = null;
			string password = null;
			string credentialRetrieval = null;
			string connectionString = null;
			bool publish = false;
			bool overwrite = false;
			bool windowsCredentials = false;

			XmlNode n1 = node.Attributes["name"];

			if ( n1 != null )
			{
				name = ProcessGlobals(n1.Value);
				XmlNode n2 = node.Attributes["publish"];
				XmlNode n3 = node.SelectSingleNode("connection-string");
				XmlNode n4 = node.Attributes["overwrite"];
				XmlNode n5 = node.SelectSingleNode("username");
				XmlNode n6 = node.SelectSingleNode("password");
				XmlNode n7 = node.SelectSingleNode("credential-retrieval");
				XmlNode n8 = node.SelectSingleNode("windows-credentials");

				if ( n2 != null )
				{
					publish = (n2.Value.ToLower() == "true");
				}
				if ( n3 != null )
				{
					connectionString = ProcessGlobals(n3.InnerText);
				}
				if ( n4 != null )
				{
					overwrite = (n4.Value.ToLower() == "true");
				}
				if ( n5 != null )
				{
					if ( n5.InnerText.Trim().Length > 0 )
					{
						userName = ProcessGlobals(n5.InnerText);
					}
				}
				if ( n6 != null )
				{
					password = ProcessGlobals(n6.InnerText);
				}
				if ( n7 != null )
				{
					credentialRetrieval = ProcessGlobals(n7.InnerText);
				}
				if ( n8 != null )
				{
					windowsCredentials = (n8.InnerText.ToLower() == "true");
				}

				DataSource datasource = new DataSource(name, folder, userName, password, credentialRetrieval, windowsCredentials, connectionString, publish, overwrite);
				folder.Datasources.Add(datasource);
				this.datasources.Add(name, datasource);
			}
		}

		private void SetOptionalItemProperties(DeploymentItem item, XmlNode sourceNode)
		{
			if ( sourceNode.Attributes["hidden"] != null && sourceNode.Attributes["hidden"].Value.Length > 0)
			{
				item.Hidden = bool.Parse(this.NodeAttribute(sourceNode, "hidden"));
			}
			if ( sourceNode["description"] != null )
			{
				item.Description = this.NodeValue(sourceNode["description"]);
			}
			XmlNodeList policyList = sourceNode.SelectNodes("policies/policy");
			if ( policyList != null )
			{
				foreach ( XmlNode policyNode in policyList )
				{
					this.AddPolicy(policyNode, item);
				}
			}
		}

		private string NodeAttribute(XmlNode node, string name)
		{
			return this.ProcessGlobals(node.Attributes[name].Value);
		}

		private string NodeValue(XmlNode node)
		{
			return this.ProcessGlobals(node.InnerText);
		}

		private string ProcessGlobals(string input)
		{
			if ( string.IsNullOrEmpty(input) )
			{
				return null;
			}
			string output = input;
			MatchCollection matches = GlobalsRegex.Matches(input);
			output = GlobalsRegex.Replace(output, new MatchEvaluator(this.ReplaceMatches));
			return output;
		}

		private string ReplaceMatches(Match match)
		{
			string key = match.Groups["k"].ToString();
			string toReplace = match.Groups["g"].ToString();
			string output = toReplace;
			if ( globalVariables.ContainsKey(key) )
			{
				output = globalVariables[key];
			}
			return output;
		}

		private void ReadGlobals(XmlDocument fromDoc)
		{
			XmlNodeList globalNodes = fromDoc.SelectNodes("//deployment/globals/global");
			globalVariables = new StringDictionary();

			if ( globalNodes != null )
			{
				foreach ( XmlNode node in globalNodes )
				{
					XmlNode key = node.Attributes["name"];
					if ( key != null )
					{
						if ( globalVariables.ContainsKey(key.Value) )
						{
							globalVariables[key.Value] = node.InnerText;
						}
						else
						{
							globalVariables.Add(key.Value, node.InnerText);
						}
					}
				}
			}
		}

		#endregion
	}
}